package cn.woblog.recyclerviewsample.animator;

import android.support.v4.animation.AnimatorCompatHelper;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.support.v7.widget.SimpleItemAnimator;
import android.view.View;
import android.view.animation.BounceInterpolator;
import android.view.animation.DecelerateInterpolator;
import java.util.ArrayList;

/**
 * Created by Pich on 17/4/7.
 */

public class SlideInLeftAnimator extends SimpleItemAnimator {

  ArrayList<ArrayList<ViewHolder>> mAdditionsList = new ArrayList<>();
  ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>();
  ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>();
  ArrayList<ViewHolder> mAddAnimations = new ArrayList<>();
  ArrayList<ViewHolder> mMoveAnimations = new ArrayList<>();
  ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>();
  ArrayList<ViewHolder> mChangeAnimations = new ArrayList<>();
  private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<>();
  private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<>();
  private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();
  private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>();

  @Override
  public boolean animateRemove(ViewHolder holder) {
    resetAnimation(holder);
    mPendingRemovals.add(holder);
    return true;
  }

  /**
   * 当需要有动画执行时，调用 在这里我们将holder添加到一个集合
   */
  @Override
  public boolean animateAdd(ViewHolder holder) {
    resetAnimation(holder);
    ViewCompat.setTranslationX(holder.itemView, -holder.itemView.getRootView().getWidth());
    mPendingAdditions.add(holder);
    return true;
  }

  private void resetAnimation(ViewHolder holder) {
    AnimatorCompatHelper.clearInterpolator(holder.itemView);
    endAnimation(holder);
  }

  @Override
  public boolean animateMove(ViewHolder holder, int fromX, int fromY, int toX, int toY) {
    final View view = holder.itemView;
    fromX += ViewCompat.getTranslationX(holder.itemView);
    fromY += ViewCompat.getTranslationY(holder.itemView);
    resetAnimation(holder);
    int deltaX = toX - fromX;
    int deltaY = toY - fromY;
    if (deltaX == 0 && deltaY == 0) {
      dispatchMoveFinished(holder);
      return false;
    }
    if (deltaX != 0) {
      ViewCompat.setTranslationX(view, -deltaX);
    }
    if (deltaY != 0) {
      ViewCompat.setTranslationY(view, -deltaY);
    }
    mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
    return true;
  }

  @Override
  public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
      int fromX, int fromY, int toX, int toY) {
    if (oldHolder == newHolder) {
      // Don't know how to run change animations when the same view holder is re-used.
      // run a move animation to handle position changes.
      return animateMove(oldHolder, fromX, fromY, toX, toY);
    }
    final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
    final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
    final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
    resetAnimation(oldHolder);
    int deltaX = (int) (toX - fromX - prevTranslationX);
    int deltaY = (int) (toY - fromY - prevTranslationY);
    // recover prev translation state after ending animation
    ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
    ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
    ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
    if (newHolder != null) {
      // carry over translation values
      resetAnimation(newHolder);
      ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
      ViewCompat.setTranslationY(newHolder.itemView, -deltaY);
      ViewCompat.setAlpha(newHolder.itemView, 0);
    }
    mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
    return true;
  }

  @Override
  public void runPendingAnimations() {
    boolean removalsPending = !mPendingRemovals.isEmpty();
    boolean movesPending = !mPendingMoves.isEmpty();
    boolean changesPending = !mPendingChanges.isEmpty();
    boolean additionsPending = !mPendingAdditions.isEmpty();
    if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
      // 如果没有要执行的动画，就直接返回
      return;
    }

    //先执行，需要删除的动画
    for (ViewHolder holder : mPendingRemovals) {
      animateRemoveImpl(holder);
    }

    mPendingRemovals.clear();

    // Next, 执行移动动画
    if (movesPending) {
      final ArrayList<MoveInfo> moves = new ArrayList<>();
      moves.addAll(mPendingMoves);
      mMovesList.add(moves);
      mPendingMoves.clear();
      Runnable mover = new Runnable() {
        @Override
        public void run() {
          for (MoveInfo moveInfo : moves) {
            animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
                moveInfo.toX, moveInfo.toY);
          }
          moves.clear();
          mMovesList.remove(moves);
        }
      };
      if (removalsPending) {
        View view = moves.get(0).holder.itemView;
        ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
      } else {
        mover.run();
      }
    }

    // Next, 执行更新动画
    if (changesPending) {
      final ArrayList<ChangeInfo> changes = new ArrayList<>();
      changes.addAll(mPendingChanges);
      mChangesList.add(changes);
      mPendingChanges.clear();
      Runnable changer = new Runnable() {
        @Override
        public void run() {
          for (ChangeInfo change : changes) {
            animateChangeImpl(change);
          }
          changes.clear();
          mChangesList.remove(changes);
        }
      };
      if (removalsPending) {
        ViewHolder holder = changes.get(0).oldHolder;
        ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
      } else {
        changer.run();
      }
    }

// 判断是否需要执行添加动画
    if (additionsPending) {
      final ArrayList<ViewHolder> additions = new ArrayList<>();
      additions.addAll(mPendingAdditions);
      mAdditionsList.add(additions);
      mPendingAdditions.clear();
      Runnable adder = new Runnable() {
        @Override
        public void run() {
          for (ViewHolder holder : additions) {
            animateAddImpl(holder);
          }
          additions.clear();
          mAdditionsList.remove(additions);
        }
      };

      if (removalsPending || movesPending || changesPending) {
        long removeDuration = removalsPending ? getRemoveDuration() : 0;
        long moveDuration = movesPending ? getMoveDuration() : 0;
        long changeDuration = changesPending ? getChangeDuration() : 0;
        long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
        View view = additions.get(0).itemView;
        ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
      } else {
        adder.run();
      }
    }
  }

  void animateChangeImpl(final ChangeInfo changeInfo) {
    final ViewHolder holder = changeInfo.oldHolder;
    final View view = holder == null ? null : holder.itemView;
    final ViewHolder newHolder = changeInfo.newHolder;
    final View newView = newHolder != null ? newHolder.itemView : null;
    if (view != null) {
      final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
          getChangeDuration());
      mChangeAnimations.add(changeInfo.oldHolder);
      oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
      oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
      oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
        @Override
        public void onAnimationStart(View view) {
          dispatchChangeStarting(changeInfo.oldHolder, true);
        }

        @Override
        public void onAnimationEnd(View view) {
          oldViewAnim.setListener(null);
          ViewCompat.setAlpha(view, 1);
          ViewCompat.setTranslationX(view, 0);
          ViewCompat.setTranslationY(view, 0);
          dispatchChangeFinished(changeInfo.oldHolder, true);
          mChangeAnimations.remove(changeInfo.oldHolder);
          dispatchFinishedWhenDone();
        }
      }).start();
    }
    if (newView != null) {
      final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView);
      mChangeAnimations.add(changeInfo.newHolder);
      newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).
          alpha(1).setListener(new VpaListenerAdapter() {
        @Override
        public void onAnimationStart(View view) {
          dispatchChangeStarting(changeInfo.newHolder, false);
        }

        @Override
        public void onAnimationEnd(View view) {
          newViewAnimation.setListener(null);
          ViewCompat.setAlpha(newView, 1);
          ViewCompat.setTranslationX(newView, 0);
          ViewCompat.setTranslationY(newView, 0);
          dispatchChangeFinished(changeInfo.newHolder, false);
          mChangeAnimations.remove(changeInfo.newHolder);
          dispatchFinishedWhenDone();
        }
      }).start();
    }
  }

  private void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) {
    final View view = holder.itemView;
    final int deltaX = toX - fromX;
    final int deltaY = toY - fromY;
    if (deltaX != 0) {
      ViewCompat.animate(view).translationX(0);
    }
    if (deltaY != 0) {
      ViewCompat.animate(view).translationY(0);
    }
    // TODO: make EndActions end listeners instead, since end actions aren't called when
    // vpas are canceled (and can't end them. why?)
    // need listener functionality in VPACompat for this. Ick.
    final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
    mMoveAnimations.add(holder);
    animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() {
      @Override
      public void onAnimationStart(View view) {
        dispatchMoveStarting(holder);
      }

      @Override
      public void onAnimationCancel(View view) {
        if (deltaX != 0) {
          ViewCompat.setTranslationX(view, 0);
        }
        if (deltaY != 0) {
          ViewCompat.setTranslationY(view, 0);
        }
      }

      @Override
      public void onAnimationEnd(View view) {
        animation.setListener(null);
        dispatchMoveFinished(holder);
        mMoveAnimations.remove(holder);
        dispatchFinishedWhenDone();
      }
    }).start();
  }

  private void animateRemoveImpl(final ViewHolder holder) {
    final View view = holder.itemView;
    final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
    mRemoveAnimations.add(holder);
    animation.setDuration(getRemoveDuration())
        .setInterpolator(new DecelerateInterpolator())
        .translationX(-view.getRootView().getWidth()).setListener(new VpaListenerAdapter() {
      @Override
      public void onAnimationStart(View view) {
        dispatchRemoveStarting(holder);
      }

      @Override
      public void onAnimationEnd(View view) {
        animation.setListener(null);
        ViewCompat.setTranslationX(view, 0);
        dispatchRemoveFinished(holder);
        mRemoveAnimations.remove(holder);
        dispatchFinishedWhenDone();
      }
    }).start();
  }

  void animateAddImpl(final ViewHolder holder) {
    final View view = holder.itemView;
    final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
    mAddAnimations.add(holder);
    animation.translationX(0).setInterpolator(new BounceInterpolator())
        .setDuration(getAddDuration()).
        setListener(new VpaListenerAdapter() {
          @Override
          public void onAnimationStart(View view) {
            dispatchAddStarting(holder);
          }

          @Override
          public void onAnimationCancel(View view) {
            ViewCompat.setAlpha(view, 1);
          }

          @Override
          public void onAnimationEnd(View view) {
            animation.setListener(null);
            dispatchAddFinished(holder);
            mAddAnimations.remove(holder);
            dispatchFinishedWhenDone();
          }
        }).start();
  }

  void dispatchFinishedWhenDone() {
    if (!isRunning()) {
      dispatchAnimationsFinished();
    }
  }

  @Override
  public void endAnimation(ViewHolder item) {

  }

  @Override
  public void endAnimations() {

  }

  @Override
  public boolean isRunning() {
    return false;
  }

  private static class VpaListenerAdapter implements ViewPropertyAnimatorListener {

    VpaListenerAdapter() {
    }

    @Override
    public void onAnimationStart(View view) {
    }

    @Override
    public void onAnimationEnd(View view) {
    }

    @Override
    public void onAnimationCancel(View view) {
    }
  }

  private static class MoveInfo {

    public ViewHolder holder;
    public int fromX, fromY, toX, toY;

    MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) {
      this.holder = holder;
      this.fromX = fromX;
      this.fromY = fromY;
      this.toX = toX;
      this.toY = toY;
    }
  }

  private static class ChangeInfo {

    public ViewHolder oldHolder, newHolder;
    public int fromX, fromY, toX, toY;

    private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) {
      this.oldHolder = oldHolder;
      this.newHolder = newHolder;
    }

    ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder,
        int fromX, int fromY, int toX, int toY) {
      this(oldHolder, newHolder);
      this.fromX = fromX;
      this.fromY = fromY;
      this.toX = toX;
      this.toY = toY;
    }

    @Override
    public String toString() {
      return "ChangeInfo{" +
          "oldHolder=" + oldHolder +
          ", newHolder=" + newHolder +
          ", fromX=" + fromX +
          ", fromY=" + fromY +
          ", toX=" + toX +
          ", toY=" + toY +
          '}';
    }
  }
}
